/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.osdtsystem.hierarchy;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.osdtsystem.utils.Storage;
import org.osdtsystem.dataset.Classification;
import org.osdtsystem.dataset.ClassificationEngine;
import org.osdtsystem.dataset.Dataset;
import org.osdtsystem.dataset.HierarchyView;

/**
 * A simple tree-based hierarchy.
 * @author Matthias Buch-Kromann
 */
public class TreeHierarchy extends AbstractSimpleHierarchy<SimpleHClass> {
    private final HashMap<String, SimpleHClass> classes;
    private final HashMap<SimpleHClass, SimpleHClass> immediateSuperclass;
    private final HashMap<SimpleHClass, Set<SimpleHClass>> immediateSubclasses;
    private final HashMap<SimpleHClass, SortedSet<SimpleHClass>> superclasses;

    /**
     *
     * @param storage
     */
    public TreeHierarchy(Storage storage) {
        super(SimpleHClass.class, storage);
        this.classes = new HashMap<String, SimpleHClass>();
        this.immediateSuperclass = new HashMap<SimpleHClass, SimpleHClass>();
        this.immediateSubclasses = new HashMap<SimpleHClass, Set<SimpleHClass>>();
        this.superclasses = new HashMap<SimpleHClass, SortedSet<SimpleHClass>>();

        // Add the top class to the hierarchy, but do not add the empty class
        // this.classes.put(SimpleHClass.TOPCLASSNAME, topClass);
        addClass(topClass, null);
    }

    @Override
    final SimpleHClass makeTopClass() {
        return new SimpleHClass(true);
    }

    @Override
    final SimpleHClass makeEmptyClass() {
        return new SimpleHClass(false);
    }

    @Override
    public boolean contains(SimpleHClass c1, SimpleHClass c2) {
        return c1 == c2 || containsProperly(c1, c2);
    }

    public boolean containsProperly(SimpleHClass class1, SimpleHClass class2) {
        return superclasses.get(class2).contains(class1);
    }
    
    @Override
    SimpleHClass intersectionBinary(SimpleHClass class1, SimpleHClass class2) {
        // Are the classes at the same depth?
        int depth1 = class1.depth();
        int depth2 = class2.depth();
        if (depth1 == depth2) {
            return class1.equals(class2) ? class1 : emptyClass();
        }

        // Classes are now at different depths. Find highest and lowest class.
        SimpleHClass highest = depth1 < depth2 ? class1 : class2;
        SimpleHClass lowest = depth1 < depth2 ? class2 : class1;

        // Now check whether the highest class contains the lowest. If not, the
        // intersection is empty (since the hierarchy is a tree).
        return superclasses.get(lowest).contains(highest) ? lowest : emptyClass();
    }

    /**
     * Return the class corresponding to a given string.
     * @param string the string
     * @return the corresponding class
     */
    public SimpleHClass hclass(String string) {
        return classes.get(string);
    }

    @Override
    public SimpleHClass dataClass(HierarchyView view, Dataset dataset, int id) {
        return hclass(view.feature(dataset, id));
    }

    @Override
    public String className(SimpleHClass hclass) {
        return hclass.toString();
    }

    @Override
    public Classification<SimpleHClass> classify(SimpleHClass root,HierarchyView<SimpleHClass> view, Dataset dataset) {
        // Create new classification
        Classification<SimpleHClass> classification
                = new ClassificationEngine<SimpleHClass>(storageAbstractFactory, this, root);
        int rootDepth = root.depth();

        // Process all data points in the data set
        for (int identifier : dataset) {
            // Find class corresponding to data point in view
            SimpleHClass dataClass = dataClass(view, dataset, identifier);

            // Check whether data class is below root
            if (containsProperly(root, dataClass)) {
                classification.addDataPoint(dataClass, identifier);

                // Process all super classes of the data class
                for (SimpleHClass superClass : superclasses.get(dataClass)) {
                    if (superClass.depth() > rootDepth && containsProperly(root, superClass))
                        classification.addDataPoint(superClass, identifier);
                }
            }
        }

        // Clean up classification
        classification.trim();
        return classification;
    }

    /**
     * Add a class to the class hierarchy.
     * @param name the name of the class
     * @param parent the immediate parent class
     * @return the new hierarchy class
     */
    public SimpleHClass addClass(String name, SimpleHClass parent) {
        // Check that hierarchy doesn't contain class already
        if (classes.containsKey(name))
            throw new IllegalArgumentException("Cannot create class " + name
                    + " twice in tree hierarchy " + this);

        // Check that all super classes exist and calculate depth
        if (parent != null && ! superclasses.containsKey(parent))
            throw new IllegalArgumentException("Non-existent super class " +
                    parent + " when adding class " + name
                        + " to tree hierarchy " + this);

        // Create class and add it to the hierarchy
        int depth = parent != null ? parent.depth() + 1 : 0;
        SimpleHClass hclass = new SimpleHClass(name, depth);
        classes.put(name, hclass);
        addClass(hclass, parent);
        return hclass;
    }

    private void addClass(SimpleHClass hclass, SimpleHClass parent) {
        // Update immediateSubclasses and immediateSuperclasses
        immediateSubclasses.put(hclass, new HashSet<SimpleHClass>());

        // Update superclasses
        SortedSet<SimpleHClass> superclassSet = new TreeSet<SimpleHClass>(depthComparator());
        superclasses.put(hclass, superclassSet);

        if (parent != null) {
            // Update immediate subclass list of super class
            immediateSuperclass.put(hclass, parent);
            immediateSubclasses.get(parent).add(hclass);

            // Add transitive super clsses to super class list
            superclassSet.addAll(superclasses.get(parent));
        }
    }
    
    public SimpleHClass superclass(SimpleHClass hclass) {
        return immediateSuperclass.get(hclass);
    }

    @Override
    public SimpleHClass superclass(SimpleHClass hclass, int depth) {
        SimpleHClass result = null;
        for (SimpleHClass superclass : superclasses.get(hclass)) {
            if (superclass.depth() > depth)
                break;
            result = superclass;
        }

        // If no appropriate super class is found
        return result != null && hclass.depth() > depth
                ? result : hclass;
    }

    @Override
    public SortedSet<SimpleHClass> superclasses(SimpleHClass hclass) {
        return Collections.unmodifiableSortedSet(superclasses.get(hclass));
    }
}
